package net.wofly.common.persistence;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.wofly.common.util.GeneralHelper;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.jpa.criteria.CriteriaBuilderImpl;
import org.hibernate.jpa.criteria.predicate.InPredicate;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.*;
import java.util.Map.Entry;

/**
 * @author yongjie Gu
 *         2013-10-11
 */
public class XiNiaoDynamicSpecifications {

	//所有的操作符可以以date结尾，表示该字段是Date类型，如GTEDate
	public enum Operator {
		/**
		 * 不等
		 */
		NEQ, EQ, LIKE, LLIKE, RLIKE, GT, LT, GTE, LTE, NOTNULL, NULL,
		/**
		 * 某个字段是否在某个集合中，使用范例：search_IN_isDefault="0 1"，空格分隔多个值
		 */
		IN,
		/**
		 * like的扩展，多个like的or连接，使用范例如：search_or_ORLIKE_name="aa"
		 */
		ORLIKE
	}

	/**
	 * 把查询条件解析为结构化数据
	 * 
	 * @param searchParams
	 * @return
	 */
	private static Map<String, Collection<SearchFilter>> parseParam(Map<String, Object> searchParams) {
		Map<String, Collection<SearchFilter>> resultMap = Maps.newHashMap();
		if (searchParams == null || searchParams.size() == 0) {
			return resultMap;
		}
			
		for (Entry<String, Object> entry : searchParams.entrySet()) {
			// 过滤掉空值
			String key = entry.getKey();
			Object value = entry.getValue();
			//如果value是数组，则使用(String)value会报错，所以修改为使用toString()方法代替。
			if (value == null || StringUtils.isBlank(value.toString())) {
				continue;
			}
			//			else if (!Date.class.isInstance(value) && !java.sql.Date.class.isInstance(value) && StringUtils.isBlank((String) value)) {
			//				continue;
			//			}

			// 拆分operator与filedAttribute
			String[] names = StringUtils.split(key, "_");
			if (names.length != 3 && names.length != 2) {
				throw new IllegalArgumentException(key + " is not a valid search filter name");
			}
			if (names.length == 2) {
				String filedName = names[1];
				SearchFilter filter = new SearchFilter(filedName, names[0], value);
				if (resultMap.get("and") == null) {
					resultMap.put("and", new LinkedList<SearchFilter>());
				}
				resultMap.get("and").add(filter);
			} else {
				String filedName = names[2];
				// 创建searchFilter
				SearchFilter filter = SearchFilter.newInstance(filedName, names[1], value);
				if (resultMap.get(names[0]) == null) {
					resultMap.put(names[0], new LinkedList<SearchFilter>());
				}
				resultMap.get(names[0]).add(filter);

			}
		}
		return resultMap;
	}

	/**
	 * 应用jpa组合查询条件（or的条件用括号括起来，and的也是，而这两组条件通过or进行连接）
	 * 
	 * @param searchParams
	 * @param clazz
	 * @return
	 */
	public static <T> Specification<T> builder(final Map<String, Object> searchParams, final Class<T> clazz) {
		final Map<String, Collection<SearchFilter>> map = parseParam(searchParams);
		return new Specification<T>() {
			@Override
			public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
				Predicate resultPredicate = builder.conjunction();
				if (map != null && map.size() > 0) {
					// old
					List<Predicate> allPredicates = Lists.newArrayList();
					Iterator<String> iter = map.keySet().iterator();
					while (iter.hasNext()) {
						Predicate subPredicate = builder.conjunction();// 每种类型（and、or）对应一列查询条件
						List<Predicate> predicates = Lists.newArrayList();
						String operationFlag = iter.next();
						for (SearchFilter filter : map.get(operationFlag)) {
							// nested path translate,
							// 如Task的名为"user.name"的filedName,
							// 转换为Task.user.name属性
							String[] names = StringUtils.split(filter.fieldName, ".");

							Path expression = root.get(names[0]);

							for (int j = 1; j < names.length; j++) {
								expression = expression.get(names[j]);
							}

							Operator operator = Operator.NULL;
							Object filterValue = filter.value;
							// logic operator
							if (filter.operator.toLowerCase().endsWith("date")) {
								operator = Operator.valueOf(filter.operator.substring(0, filter.operator.length() - 4));
								filterValue = praseDate(filter.value.toString());
							} else {
								operator = Operator.valueOf(filter.operator);
							}
							switch (operator) {
							case NEQ:
								predicates.add(builder.notEqual(expression, filterValue));
								break;
							case EQ:
								predicates.add(builder.equal(expression, filterValue));
								break;
							case LIKE:
								predicates.add(builder.like(expression, "%" + filterValue + "%"));
								break;
							case LLIKE:
								predicates.add(builder.like(expression, filterValue + "%"));
								break;
							case RLIKE:
								predicates.add(builder.like(expression, "%" + filterValue));
								break;
							case GT:
								predicates.add(builder.greaterThan(expression, (Comparable) filterValue));
								break;
							case LT:
								predicates.add(builder.lessThan(expression, (Comparable) filterValue));
								break;
							case GTE:
								predicates.add(builder.greaterThanOrEqualTo(expression, (Comparable) filterValue));
								break;
							case LTE:
								predicates.add(builder.lessThanOrEqualTo(expression, (Comparable) filterValue));
								break;
							case NOTNULL:
								predicates.add(builder.isNotNull(expression));
								break;
							case NULL:
								predicates.add(builder.isNull(expression));
								break;
							case IN:
								List<T> list = new ArrayList<T>();
								String value[] = filterValue.toString().split("[\\s]");
								if (filter.value instanceof String[]) {
									list = (List<T>) Arrays.asList((String[]) filterValue);
								} else if (filter.value instanceof List) {
									list = (List<T>) filter.value;
								} else {
									for (String now : value) {
										list.add((T) (now));
									}
								}
								predicates.add(new InPredicate<T>((CriteriaBuilderImpl) builder, expression, list));
								break;
							case ORLIKE:
								value = filterValue.toString().split("[\\s]");
								for (String now : value) {
									predicates.add(builder.like(expression, "%" + now + "%"));
								}
								break;
							default :
								
							}
						}

						if (operationFlag.startsWith("and")) {
							subPredicate = builder.and(predicates.toArray(new Predicate[predicates.size()]));
						} else if (operationFlag.startsWith("or")) {
							subPredicate = builder.or(predicates.toArray(new Predicate[predicates.size()]));
						}
						allPredicates.add(subPredicate);
					}
					return builder.and(allPredicates.toArray(new Predicate[allPredicates.size()]));
				}
				return builder.conjunction();
			}
		};
	}

	/**
	 * @Description: 将一个字符串转换成日期类型，支持包含时间的日期，解析时按非数字进行分隔
	 * @param src
	 * @return Date 返回类型
	 */
	private static Date praseDate(String src) {
		//如果该字符串可以解析成一个long
		Long l = GeneralHelper.str2Long(src);
		if (l != null) {
			return new Date(l);
		}
		//普通格式，如2012-01-01 11:11:11
		String[] temp = src.trim().split("[\\D]+");
		Calendar calendar = Calendar.getInstance();
		int[] field = new int[] { Calendar.YEAR, Calendar.MONTH, Calendar.DAY_OF_MONTH, Calendar.HOUR_OF_DAY, Calendar.MINUTE, Calendar.SECOND };
		for (int i = 0; i < field.length; i++) {
			if (i < temp.length) {
				if (i == 1) {
					calendar.set(field[i], GeneralHelper.str2Int(temp[i], 0) - 1);
				}
				else {
					calendar.set(field[i], GeneralHelper.str2Int(temp[i], 0));
				}
			} else {
				calendar.set(field[i], 0);
			}
		}
		return calendar.getTime();
	}

	static class SearchFilter {

		public String fieldName;
		public Object value;
		public String operator;

		private SearchFilter(String fieldName, String operator, Object value) {
			this.fieldName = fieldName;
			this.value = value;
			this.operator = operator;
		}

		public static SearchFilter newInstance(String fieldName, String operator, Object value) {
			return new SearchFilter(fieldName, operator, value);
		}
	}
}
